/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <plat/uart.h>
#include <stdlib/assert.h>
#include <core/dprintf.h>
#include <core/spinlock.h>

static const char xdigitmap[] = "0123456789abcdef";
static const char Xdigitmap[] = "0123456789ABCDEF";

static spinlock_t kernel_serial_lock  = SPINLOCK_INIT_UNLOCKED;
static int        available_lock_flag = 0;

static void print_prefix(void);

static char *fmt_d(char *buf, char *bufend, int64_t value, char long_flag)
{
    char   *buf_end, *buf_temp = buf;
    int64_t value_temp = value;
    if ((0 == long_flag) && ((value_temp >> 31) & 1)) {
        value_temp = (int32_t)value_temp;
        value      = (int32_t)value;
        value_temp = -value_temp;
        value      = -value;
        *buf_temp  = '-', buf_temp++;
    } else if ((1 == long_flag) && (value_temp) < 0) {
        value_temp = -value_temp;
        value      = -value;
        *buf_temp  = '-', buf_temp++;
    }
    if (0 == value_temp) {
        buf_temp++;
        buf_end = buf_temp;
        buf_temp--;
        *buf_temp = xdigitmap[value % 10], buf_temp--;
    } else {
        while (value_temp && (buf_temp < bufend)) {
            buf_temp++;
            value_temp /= 10;
        }
        buf_end = buf_temp;
        buf_temp--;
        while ((buf_temp >= buf) && value) {
            *buf_temp = xdigitmap[value % 10], buf_temp--;
            value /= 10;
        }
    }
    return buf_end;
}

static char *fmt_b(char *buf, char *bufend, int64_t value)
{
    char   *buf_end, *buf_temp = buf;
    int64_t value_temp = value;
    if (0 == value_temp) {
        *buf_temp = '0';
        return buf_temp + 1;
    }
    while (value_temp && (buf_temp < bufend)) {
        buf_temp++;
        value_temp = value_temp >> 1;
    }
    buf_end = buf_temp;
    buf_temp--;
    while ((buf_temp >= buf) && value) {
        *buf_temp = '0' + (value & 0x1), buf_temp--;
        value     = value >> 1;
    }
    return buf_end;
}

static char *fmt_x(char *buf, char *bufend, uint64_t value, int X)
{
    const char *map   = xdigitmap;
    uint64_t    len   = 0;
    uint64_t    val_t = value;
    if ((bufend - buf) > 2) {
        *(buf++) = '0';
        *(buf++) = 'x';
    }
    while (val_t > 0) {
        val_t >>= 1;
        len++;
    }
    while (len % 4) {
        len++;
    }

    if (X != 'x') {
        map = Xdigitmap;
    }

    while ((buf < bufend) && (len > 4)) {
        len -= 4;
        *buf = map[(value >> len) & 0xf], ++buf;
    }

    if (buf < bufend) {
        *buf = map[value & 0xf], ++buf;
    }

    return buf;
}

static char *fmt_s(char *buf, char *bufend, const char *s)
{
    while ((buf < bufend) && (*s != '\0')) {
        *buf = *s, ++buf;
        ++s;
    }

    return buf;
}

int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
{
    char *const bufstart = buf;
    char *const bufend   = buf + size - 1;
    while ((buf < bufend) && (*fmt != '\0')) {
        if (*fmt == '%') {
            ++fmt;
            switch (*fmt) {
                case 'c':
                    *buf = (char)va_arg(ap, int), buf++;
                    break;
                case 'l':
                    ++fmt;
                    if ('d' == *fmt) {
                        buf = fmt_d(buf, bufend, va_arg(ap, int64_t), 1);
                    }
                    break;
                case 'd':
                    buf = fmt_d(buf, bufend, va_arg(ap, int64_t), 0);
                    break;
                case 'x':
                    buf = fmt_x(buf, bufend, va_arg(ap, uint64_t), 'x');
                    break;
                case 'X':
                    buf = fmt_x(buf, bufend, va_arg(ap, uint64_t), 'X');
                    break;
                case 's':
                    buf = fmt_s(buf, bufend, va_arg(ap, char *));
                    break;
                case 'b':
                    buf = fmt_b(buf, bufend, va_arg(ap, int64_t));
                    break;
                default:
                    goto label_ret;
            }
        } else {
            *buf = *fmt, ++buf;
            if (*fmt == '\n') {
                *buf = '\r', ++buf;
            }
        }

        ++fmt;
    }
label_ret:
    *buf = '\0', ++buf;
    return buf - bufstart;
}

int debug_printf(const char *fmt, ...)
{
#ifdef CONFIG_ENABLE_DPRINTF
    char    buf[CONFIG_DPRINTF_BUFSIZE];
    va_list ap;
    int     count;
    char   *cur = buf;
    va_start(ap, fmt);
    count = vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    if (available_lock_flag != 0) {
        spin_lock(&kernel_serial_lock);
    }
    print_prefix();
    while (--count > 0) {
        uart_putchar(*cur);
        ++cur;
    }
    if (available_lock_flag != 0) {
        spin_unlock(&kernel_serial_lock);
    }
    return (int)(cur - buf);
#else
    return 0;
#endif
}

void print_line(void)
{
    dprintf("------------------------------\n");
}

static void print_prefix(void)
{
    uart_putchar('[');
    uart_putchar('K');
    uart_putchar(']');
    uart_putchar(' ');
}

/* ------------------ARM64------------------- */
unsigned char dprintf_regs_index = 0;
long long     dprintf_regs[7];
void          Dprintf_Get_Regs(void)
{
    volatile register long long x1_reg asm("x1");
    volatile register long long x2_reg asm("x2");
    volatile register long long x3_reg asm("x3");
    volatile register long long x4_reg asm("x4");
    volatile register long long x5_reg asm("x5");
    volatile register long long x6_reg asm("x6");
    volatile register long long x7_reg asm("x7");
    dprintf_regs[0] = x1_reg;
    dprintf_regs[1] = x2_reg;
    dprintf_regs[2] = x3_reg;
    dprintf_regs[3] = x4_reg;
    dprintf_regs[4] = x5_reg;
    dprintf_regs[5] = x6_reg;
    dprintf_regs[6] = x7_reg;
}

void set_available_lock_flag(int val)
{
    available_lock_flag = val;
}
